#
# "@(#) $Id: Library.py,v 1.1.1.1 2006-07-27 03:16:08 gioshe Exp $"
#
# This work is released under the GNU GPL, version 2 or later.
#
from Utils.kdeemul import *
from MP3Track import *
from MP4Track import *
from OggTrack import *
from WavTrack import *
from SunAUTrack import *
from AIFFTrack import *
from CDTrack import *
from RealAudioTrack import *
from Playlist import *
from Utils.PListParser import *
import os, errno, shutil, fnmatch, time
from CD.Importer import *
from Utils.Settings import *
import Utils.crushToAscii
import cPickle,time
from Utils.utils import *
_mainLibrary = None

def normalizeName(s):
	s = re.sub(r":",r"-",s)
	s = re.sub(r"/",r"-",s)
	s = re.sub(r"\\",r"-",s)
	return s

#
# return a flattened list of files that match the given pattern
# filter out any trashed files or Apple resource forks
#
def listFiles(root,patterns = '*',recurse=1,return_folders=0):
	pattern_list  = patterns.split(';')
	class Bunch:
		def __init__(self,**kwds): self.__dict__.update(kwds)
	arg = Bunch(recurse=recurse,pattern_list=pattern_list, return_folders=return_folders,results=[])
	
	def visit(arg,dirname,files):
		basename = os.path.basename(dirname)
		#print "visiting",basename
		if basename=='.AppleDouble' or basename=='Trash':
			files[:] = []
			return
		for name in files:
			fullname = os.path.normpath(os.path.join(dirname,name))
			if arg.return_folders or os.path.isfile(fullname):
				for pattern in arg.pattern_list:
					if fnmatch.fnmatch(name,pattern):
						arg.results.append(fullname)
						break
		if not arg.recurse: files[:] = []
	os.path.walk(root,visit,arg)
	return arg.results

def removeDuplicates(items):
	result = {}
	for item in items:
		result[item] = 1
	return result.keys()

class Library(object):
	def __init__(self):
		self._qObject = QObject()
		self._tracks = {}
		self._playlists = []

	def __getstate__(self):
		return [self._tracks,self._playlists]
	
	def __setstate__(self,stuff):
		(self._tracks ,self._playlists) = stuff
		self._qObject = QObject()

	def qObject(self):
		return self._qObject

	def emit(self,signal,args):
		self._qObject.emit(signal,args)

	def connect(self,signal,target):
		QObject.connect(self._qObject,signal,target)

	def clearTracks(self):
		self._tracks = {}

	def getPlaylists(self):
		return self._playlists[:]
##		playlists = []
##		for playlist in self._playlists:
##			playlists.append(playlist)
##		return playlists
	playlists = property(getPlaylists)

	def getShareablePlaylists(self):
		playlists = []
		for playlist in self._playlists:
			if not (playlist.trash or playlist.purchased or playlist.smart):
				playlists.append(playlist)
		return playlists

	def uniquePlaylistID(self):
		newID = 100
		for playlist in self._playlists:
			playlistID = playlist.playlistID
			if playlistID>=newID:
				newID = playlistID+1
		return newID

	def uniquePlaylistName(self):
		return self.uniquePlaylistNameFrom(unikode(i18n("untitled playlist")))
	
	def uniquePlaylistNameFrom(self,baseName):
		if self.playlistWithName(baseName)==None:
			return baseName
		index = 1
		while True:
			name = baseName+" "+str(index)
			if self.playlistWithName(name)==None:
				return name
			index = index+1

	def uniquePlaylistNameFromTracks(self,tracks):
		tags = None
		for track in tracks:
			tags = track.loadTagsInto(tags)
		name = None
		if tags!=None:
			if tags.has_key('title'):
				name = self.uniquePlaylistNameFrom(tags['title'])
			elif tags.has_key('artist'):
				if tags.has_key('album'):
					name = self.uniquePlaylistNameFrom("%s - %s" % (tags['artist'],tags['album']))
				else:
					name = self.uniquePlaylistNameFrom(tags['artist'])
			elif tags.has_key('album'):
				name = self.uniquePlaylistNameFrom(tags['album'])
		return name

	def addPlaylistFromTrackIDs(self,trackIDs):
		tracks = self.tracksWithTrackIDs(trackIDs)
		name = self.uniquePlaylistNameFromTracks(tracks)
		playlist = self.addNewPlaylist(name)
		playlist.addTrackIDs(trackIDs)		
		return playlist

	def playlistWithName(self,name):
		for playlist in self._playlists:
			#print type(playlist.name),type(name)
			if unikode(playlist.name)==unikode(name):
				return playlist
		return None

	def playlistWithID(self,id):
		for playlist in self._playlists:
			if playlist.playlistID == id:
				return playlist
		return None

	def containsPlaylistID(self,id):
		return self.playlistWithID(id)!=None

	def newPlaylist(self):
		return Playlist(self)
		
	def addNewPlaylist(self,name = None,master=False,trash=False):
		#print "adding playlist",name
		playlist = self.newPlaylist()
		if name!=None:
			playlist.name = name
		if master:
			playlist.master = True
		if trash:
			playlist.trash = True
		self.addPlaylist(playlist)
		return playlist

	def addPlaylist(self,playlist):
		self._playlists.append(playlist)
		self.emit(PYSIGNAL('addedPlaylist'),(self,playlist))
	
	def removePlaylist(self,playlist):
		self._playlists.remove(playlist)
		self.emit(PYSIGNAL('removedPlaylist'),(self,playlist))
	
	def playlistsWithTrack(self,track):
		return self.playlistsWithTrackID(track.trackID)

	def playlistsWithTrackID(self,trackID):
		result = []
		for playlist in self._playlists:
			if playlist.containsTrackID(trackID):
				result.append(playlist)
		return result

	def uniqueTrackID(self):
		newID = 1
		for trackKey in self._tracks.keys():
			track = self._tracks[trackKey]
			trackID = track.trackID
			if trackID>=newID:
				newID = trackID+1
		return newID

	def trackWithTrackID(self,trackID):
		try: return self._tracks[trackID]
		except: None

	def containsTrackID(self,trackID):
		return self.trackWithTrackID(trackID)!=None

	def tracksWithTrackIDs(self,trackIDs):
		result = []
		for trackID in trackIDs:
			result.append(self.trackWithTrackID(trackID))
		return result

	def addTrack(self,track):
		self._tracks[track.trackID] = track
		print "adding",track
		#import traceback
		#traceback.print_stack()
	
	def addTracks(self,tracks):
		for track in tracks:
			self.addTrack(track)

	def removeTrackIDsFromAllPlaylists(self,trackIDs):
		self.emit(PYSIGNAL("bulkEditBegin"),(self,None))
		for playlist in self._playlists:
			if not playlist.trash:
				playlist.removeTrackIDs(trackIDs)
		self.emit(PYSIGNAL("bulkEditEnd"),(self,None))

	def removeTrack(self,track):
		try: del self._tracks[track.trackID]
		except: pass

	def tracks(self):
		return self._tracks
	
	def artists(self):
		artists = {}
		for trackKey in self._tracks.keys():
			track = self._tracks[trackKey]
			artist = track.artist
			if artist!=None:
				artists[unikode(artist)] = 1
		return artists.keys()
	
	def albums(self):
		albums = {}
		for trackKey in self._tracks.keys():
			track = self._tracks[trackKey]
			album = track.album
			if album!=None:
				albums[unikode(album)] = 1
		return albums.keys()
	
	def changedTrackAttribute(self,track,name,oldValue,newValue):
		if name=='artist' or name=='album':
			self.emit(PYSIGNAL("changedTrackArtistOrAlbum"),(self,track))

	def emptyPlaylist(self):
		return Playlist(self)

	def trackForKind(self,trackPList):
		kind = trackPList['Kind']
		if kind=='MPEG audio file':
			track = MP3Track(self)
		elif kind=='MPEG-4 audio file':
			track = MP4Track(self)
		elif kind=='Ogg audio file':
			track = OggTrack(self)
		elif kind=='WAV audio file':
			track = WavTrack(self)
		elif kind=='Sun AU audio file':
			track = SunAUTrack(self)
		elif kind=='AIFF audio file' or kind=='AIFC audio file':
			track = AIFFTrack(self)
		elif kind=='Real Audio audio file':
			track = RealAudioTrack(self)
		elif kind=='Windows Media audio file':
			track = WindowsMediaTrack(self)
		elif kind=='CD Track':
			track = CDTrack(self)
		else:
			print "Can't find track of kind",kind
			track = None
		return track

	def getPList(self):
		pList = {'Major Version':1,'Minor Version':0,'Application Version':'1.0','Application Name':'Lsongs'}
		tracks = {}
		for trackKey in self._tracks.keys():
			track = self._tracks[trackKey]
			tracks[str(track.trackID)] = track.pList
		pList['Tracks'] = tracks
		playlists = []
		for playlist in self._playlists:
			playlists.append(playlist.pList)
		pList['Playlists'] = playlists
		return pList

	def setPList(self,pList):
		#print "parsing tracks"
		progress = QProgressDialog("Reading","",100,None,"progress",True)
		progress.setCaption(i18n("Building library"))
		if pList.has_key("Tracks"):
			tracks = pList['Tracks']
			progress.setTotalSteps(len(tracks))
			then = long(time.time()*4)
			index = 0
			for trackKey in tracks.keys():
				now = long(time.time()*4)
				updateProgress = False
				if now!=then:
					progress.setProgress(index)
					updateProgress = True
					then = now
				# XXX DSM handle different media types here
				trackPList = tracks[trackKey]
				track = self.trackForKind(trackPList)
				if track!=None:
					track.setPList(trackPList)
					if updateProgress: progress.setLabelText(track.title)
					self._tracks[int(track.trackID)] = track
				index = index+1
			#print "parsing playlists"
		if pList.has_key('Playlists'):
			playlists = pList['Playlists']
			progress.setCaption(i18n("Building playlists"))
			progress.setTotalSteps(len(playlists))
			index = 0
			for playlistPList in playlists:
				progress.setProgress(index)
				# XXX DSM handle smart playlists here
				playlist = self.emptyPlaylist()
				playlist.pList = playlistPList
				progress.setLabelText(playlist.name)
				self._playlists.append(playlist)
				index = index+1
			progress.setProgress(len(playlists))

	pList = property(getPList,setPList)
	
	def writeXMLFile(self):
		pass

	def findTrackLike(self,otherTrack):
		for track in self.tracks().values():
			if track.totalTime==otherTrack.totalTime and track.sampleRate==otherTrack.sampleRate and track.kind==otherTrack.kind:
				if track.getShortName()==otherTrack.getShortName():
					return track
		return None

	#
	# creates a dictionary of tracks keyed with title/artist/album string
	#
	def hashList(self):
		result = {}
		for trackID in self._tracks.keys():
			track = self._tracks[trackID]
			result[track.hashKey()] = track
		return result

	def getRemotePList(self,withTracks = False):
		settings = Settings.settings()
		pList = {'Major Version':1,'Minor Version':0,'Application Version':'1.0','Application Name':'Lsongs'}
		playlists = []
		for playlist in self._playlists:
			if (not playlist.trash) and (playlist.shared or settings.get("Share All",False)):
				playlists.append(playlist.getRemotePList(False))
		pList['Playlists'] = playlists
		pList['Share Allow Writes'] = settings.get('Share Allow Writes',False)
		return pList
	remotePList = property(getRemotePList)

	def getMainLibrary():
		global _mainLibrary
		if _mainLibrary==None:
			_mainLibrary = MainLibrary()
			lib = _mainLibrary.readPickleFile()
			if lib: _mainLibrary = lib
			elif not _mainLibrary.readBinaryFile():
				if not _mainLibrary.readXMLFile():
					_mainLibrary.addNewPlaylist("Library",master=True)
					_mainLibrary.addNewPlaylist("Trash",trash=True)
					_mainLibrary.isNew = True
					#GTD do not import files on first run 
					#_mainLibrary.importFiles([_mainLibrary.libraryParentPath()])
		return _mainLibrary

	mainLibrary = staticmethod(getMainLibrary)

class FileLibrary(Library):
	def __init__(self):
		Library.__init__(self)
		self.createExtensions()
		self.ensureDirectories()
	
	def __getstate__(self):
		return Library.__getstate__(self)
	
	def __setstate__(self,stuff):
		Library.__setstate__(self,stuff)
		self.createExtensions()

	def createExtensions(self):
		self.validExtensions = self.filter().split(';')
	
	def filter(self):
		return "*.mp3"  # patterns separated by semicolons
	
	def fileFilter(self):
		return "*.mp3|Music Files"  # patterns separated by spaces
	
	def ensureDirectories(self):
		pass

	def musicPath(self):
		return "/tmp"

	def isValidFile(self,filePath):
		if os.path.exists(filePath):
			if os.path.isdir(filePath):
				files = listFiles(filePath,self.filter())
				for file in files:
					if self.isValidFile(file):
						return True
				return False
			else:
				filePath = filePath.lower()
				for ext in self.validExtensions:
					if fnmatch.fnmatch(filePath,ext):
						return True
		else:
			print "FileLibrary::isValidFile: path doesn't exist:",path
		return False

	def canImportFiles(self,uris):
		for uri in uris:
			if self.isValidFile(uri):
				return True
		return False

	def removeExisting(self,files):
		result = []
		trash = self.playlistWithName("Trash")
		for file in files:
			found = False
			for trackKey in self._tracks.keys():
				track = self._tracks[trackKey]
				if unikode(track.location)==unikode(file) or unikode(track.originalLocation)==unikode(file):
					#print file,"already in Library"
					if trash.containsTrack(track):
						break
					found = True
					break
			if not found:
				result.append(file)
		return result

	def ensureDirectories(self):
		self.ensureDirectory(self.musicPath())

	def ensureDirectory(self,path):
		try:
			os.makedirs(path,0777)
		except OSError, err:
			if err.errno != errno.EEXIST or not os.path.isdir(path):
				raise

	def moveFile(self,fileName,fromPath,toPath):
		#print "moving "+fileName+" from "+fromPath+" to "+toPath
		self.ensureDirectory(toPath)
		newFileName = self.uniqueName(fileName,toPath)
		shutil.move(os.path.join(fromPath,fileName),os.path.join(toPath,newFileName))
		return newFileName

	def copyFile(self,fileName,fromPath,toPath):
		#print "Library copying "+fileName+" from "+fromPath+" to "+toPath
		self.ensureDirectory(toPath)
		newFileName = self.uniqueName(fileName,toPath)
		#print "new file name is",newFileName
		shutil.copy(os.path.join(fromPath,fileName),os.path.join(toPath,newFileName))
		return newFileName

	def removeIfEmpty(self,path):
		if os.path.exists(path):
			if len(os.listdir(path))==0:
				os.rmdir(path)
				(path,blah) = os.path.split(path)
				if len(os.listdir(path))==0:
					os.rmdir(path)
				return True
		return False

	def makePathFor(self,artist,album):
		if album==None or len(album)==0:
			album = "Unknown Album"
		if artist==None or len(artist)==0:
			artist = "Unknown Artist"
		artist = normalizeName(crushToAscii.crush(artist))
		album = normalizeName(crushToAscii.crush(album))
		return os.path.join(self.musicPath(),str(artist),str(album))

	def uniqueName(self,fileName,directory):
		altName = crushToAscii.crush(fileName)
		#print "Library checking for",altName,"in",directory
		if os.path.exists(os.path.join(directory,altName)):
			#print "Library name collision",directory,altName
			(root,ext) = os.path.splitext(fileName)
			index = 1
			while True:
				altName = root+" "+str(index)+ext
				if not os.path.exists(os.path.join(directory,altName)):
					break
				index = index+1
		return altName

	def killTrackIDs(self,trackIDs):
		self.emit(PYSIGNAL("bulkEditBegin"),(self,None))
		for trackID in trackIDs:
			track = self.trackWithTrackID(trackID)
			if track:
				track.kill()
			try: del self._tracks[trackID]
			except: print "FileLibrary::killTrackIDs() unknown trackID %d" % trackID
		self.emit(PYSIGNAL("bulkEditEnd"),(self,None))

class LWriteAltFilesThread(QThread):
	def __init__(self,library):
		QThread.__init__(self)
		self.library = library
	
	def run(self):
		self.library.doWriteAltFiles()

class MainLibrary(FileLibrary):
	def __init__(self):
		FileLibrary.__init__(self)
		self.initStuff()
	
	def __getstate__(self):
		return FileLibrary.__getstate__(self)
	
	def __setstate__(self,stuff):
		FileLibrary.__setstate__(self,stuff)
		self.initStuff()
	
	def initStuff(self):
		self.importsComplete = False
		self.shouldPlayAfterImport = False
		self.lastSaveTime = time.time()
		self.saveTimer = None
		self.resetSaveTimer()
		self.dirty = False
		self.isNew = False
		self.writingFile = False
	
	def killSaveTimer(self):
		if self.saveTimer:
			self.saveTimer.stop()
			self.saveTimer = None

	def resetSaveTimer(self):
		#print "starting autosave timer"
		if self.saveTimer ==None:
			self.saveTimer = QTimer()
			QObject.connect(self.saveTimer,SIGNAL("timeout()"),self.autoSave)
		self.saveTimer.stop() # should be redundant
		self.saveTimer.start(3*60*1000,True) # autosave every 3 minutes if the database is dirty

	def markDirty(self):
		#print "marking Library dirty"
		self.dirty = True
##		now = time.time()
##		if now-self.lastSaveTime>9*60*1000: # force save now if longer than 9 minutes since last save
##			self.autoSave()
	
	def autoSave(self):
		#print "checking for autosave"
		if self.dirty:
			print "MainLibrary.autosave"
			self.dirty = False
			self.writeFiles()
			self.lastSaveTime = time.time()
		self.resetSaveTimer()

	def filter(self):
		return "*.mp3;*.MP3;*.ogg;*.OGG;*.wav;*.WAV;*.au;*.AU;*.aiff;*.AIFF;*.aifc;*.AIFC;*.ra;*.RA;*.wmx;*.WMX;*.wma;*.WMA;*.m4a;*.M4A"

	def fileFilter(self):
		return "*.mp3 *.MP3 *.m4a *.M4A *.ogg *.OGG *.wav *.WAV *.au *.AU *.aiff *.AIFF *.aifc *.AIFC *.ra *.RA *.wmx *.WMX *.wma *.WMA|Music Files"

	
	def libraryParentPath(self):
		return os.getenv("HOME")
	
	def libraryPath(self):
		return os.path.join(self.libraryParentPath(),'.8traK')

	def xmlPath(self):
		return os.path.join(self.libraryPath(),'Library.xml')

	def binaryPath(self):
		return os.path.join(self.libraryPath(),'Library.bin')
		
	def picklePath(self):
		return os.path.join(self.libraryPath(),'Library.pickle')

	def musicPath(self):
		return Settings.settings().get("Main Library Path",self.defaultMusicPath())
	
	def defaultMusicPath(self):
		return os.path.join(self.libraryPath(),'Music')

	def importFiles(self,uris,playlist = None,shouldPlayAfterImport = False):
		#print "importing files",uris," to playlist",playlist
		self.shouldPlayAfterImport = shouldPlayAfterImport
		files = []
		for uri in uris:
			if os.path.exists(uri):
				if os.path.isdir(uri):
					files.extend(listFiles(uri,self.filter()))
				else:
					files.append(uri)
		files = self.removeExisting(files)
		#print "importing",files
		if len(files)>0:
			files = removeDuplicates(files)
			files.sort()
			Importer.singleton().importFiles(files,self,playlist)
		elif shouldPlayAfterImport:
			trackObj = self.playlistWithName("Library").trackWithLocation(uris[-1])
			if trackObj:
				Player.singleton().playTrack(trackObj)
	
	def finishImportFile(self,track,playlist = None,trackInfo=None):
		masterList = self.playlistWithName("Library")
		track.addToLibrary(self)
		if masterList!=None:
			masterList.addTrack(track)
		if playlist!=None:
			t = type(playlist).__name__
			if t=='list':
				playlists = playlist
				for playlist in playlists:
					if playlist!=masterList:
						playlist.addTrack(track)
			else:
				if playlist!=masterList:
					playlist.addTrack(track)
		if self.importsComplete:
			print "EMITINF ADDEDTRACKS SIGNAL"
			self.emit(PYSIGNAL('addedTracks'),(self,None))
			if self.shouldPlayAfterImport and track:
				Player.singleton().playTrack(track)
				self.shouldPlayAfterImport = False
			self.importsComplete=False
		try: self.markDirty()
		except: pass

	#
	# simple synchronous import of a single file,
	# used by the CD ripper
	#
	def importFile(self,filePath,managed = False,playlists = None):
		# XXX DSM handle other rip file types here
		if type(filePath).__name__=='unicode':
			filePath = filePath.encode('latin-1')
		track = self.trackForFilePath(filePath)
		print "importing",[filePath],"into",track
		track.setFile(filePath)
		track.managed = True
		self.finishImportFile(track,playlists)
		try: self.markDirty()
		except: pass
		return track

	def trackForFilePath(self,fullPath):
		(srcPath,fileName) = os.path.split(fullPath)
		(root,ext) = os.path.splitext(fileName)
		ext = ext.lower()
		track = None
		if ext=='.mp3':
			track = MP3Track()
		elif ext=='.m4a':
			track = MP4Track()
		elif ext=='.ogg':
			track = OggTrack()
		elif ext=='.wav':
			track = WavTrack()
		elif ext=='.au':
			track = SunAUTrack()
		elif ext=='.aiff' or ext=='.aifc':
			track = AIFFTrack()
		elif ext=='.ra':
			track = RealAudioTrack()
		elif ext=='.wmx' or ext=='.wma':
			track = WindowsMediaTrack()
		return track

	def readXMLFile(self):
		#print "reading XML file"
		fileName = self.xmlPath()
		if os.path.exists(fileName):
			try:
				reader = PListReader()
				pList = reader.parse(fileName)
				self.pList = pList[0]
				self.sanityCheck()
			except: return False
			self.emit(PYSIGNAL("readLibrary"),(None,None))
			return True
		else:
			return False

	def readBinaryFile(self):
		#print "reading binary file"
		fileName = self.binaryPath()
		if os.path.exists(fileName):
			try:
				#print "about to read binary"
				fd = open(fileName,"rb")
				self.pList = cPickle.load(fd)
				self.sanityCheck()
				fd.close()
				#print "successfully read binary"
			except: return False
			self.emit(PYSIGNAL("readLibrary"),(self,None))
			return True
		else:
			return False

	#
	# check to make sure some basic things are working from the
	# loaded file - like there's a "Library" and a "Trash" and
	# that there are no "orphan" tracks
	#
	# there's a separate command to identify references to files
	# that no longer exist, or bring in files in the managed
	# directories that have no existing references
	#
	def sanityCheck(self):
		#
		# be sure there's a Library
		#
		mainlib = self.playlistWithName("Library")
		if mainlib==None:
			# Library missing, make one and add tracks
			print "missing Media Library"
			mainlib = self.addNewPlaylist("Library")
			mainlib.master = True
			for trackKey in self._tracks.keys():
				track = self._tracks[trackKey]
				mainlib.addTrack(track)
		#
		# be sure there's a Trash
		#
		trash = self.playlistWithName("Trash")
		if trash==None:
			print "missing Trash"
			# Trash missing, make an empty one
			trash = self.addNewPlaylist("Trash")
			trash.trash = True
		#
		# move any tracks not in the Library into
		# the Trash
		#
		# also cleanup any load artifacts in the track
		#
		for trackKey in self._tracks.keys():
			track = self._tracks[trackKey]
			if not mainlib.containsTrack(track):
				if not trash.containsTrack(track):
					trash.addTrack(track)
			else:
				track.cleanup()
	
	def readPickleFile(self):
		fileName = self.picklePath()
		if os.path.exists(fileName):
			lib = None
			try:
				#print "about to read binary"
				start = time.time()
				fd = open(fileName,"rb")
				lib= cPickle.load(fd)
				lib.sanityCheck()
				fd.close()
				end = time.time()
				print "Library read primary database in",end-start,"seconds"
				self.emit(PYSIGNAL("readLibrary"),(self,None))
			except: pass
			return lib
		return None
		
	def writeFiles(self):
		self.writePickleFile()
		self.writeAltFiles()

	def doWriteAltFiles(self):
		plist = self.getPList()
		self.writeBinaryFile(plist)
		self.writeXMLFile(plist)

	def writeBinaryFile(self,plist = None):
		if not self.writingFile:
			start = time.time()
			self.writingFile = True
			try:
				if plist==None:
					plist = self.getPList()
				fd = open(self.binaryPath(),"wb")
				try: cPickle.dump(plist,fd,cPickle.HIGHEST_PROTOCOL)
				except: cPickle.dump(self.getPList(),fd,True)
				fd.close()
			except: pass
			end = time.time()
			print "Library: wrote backup database file in",end-start,"seconds"
			self.writingFile = False

	def writePickleFile(self):
		#print "writing pickle file"
		if not self.writingFile:
			start = time.time()
			self.writingFile = True
			fd = open(self.picklePath(),"wb")
			#try:
			self.killSaveTimer()
			cPickle.dump(self,fd,cPickle.HIGHEST_PROTOCOL)
			self.resetSaveTimer()
			#except: pass
			fd.close()
			self.writingFile = False
			end = time.time()
			print "Library: wrote primary database in",end-start,"seconds"

	def writeAltFiles(self):
		self.altFilesThread = LWriteAltFilesThread(self)
		self.altFilesThread.start(QThread.LowPriority)

	#
	# "safe-write" the XML file - we write to a temporary file - when we're
	# done, delete the original, and rename the temporary - the eliminates
	# any collisions of the file caused by some other program reading the file
	# while we're still writing it - now the worst case is someone reads the old file
	#
	def writeXMLFile(self,plist = None):
		if not self.writingFile:
			self.writingFile = True
			try:
				start = time.time()
				if plist==None:
					plist = self.getPList()
				#print "writing XML file"
				#import traceback
				#print traceback.print_stack()
				normalName= self.xmlPath()
				tempName = normalName+"x"
				writer = PListWriter()
				writer.unparse([plist],tempName)
				try: os.unlink(normalName)
				except: pass
				try: os.rename(tempName,normalName)
				except: pass
			except: pass
			end = time.time()
			print "Library: wrote XML database in",end-start,"seconds"
			self.writingFile = False
		
	def emptyTrash(self):
		trash = self.playlistWithName("Trash")
		trackIDs = trash.getTrackIDs()
		self.removeTrackIDsFromTrash(trackIDs)
	
	def removeTrackIDsFromTrash(self,trackIDs):
		trash = self.playlistWithName("Trash")
		trash.removeTrackIDs(trackIDs)
		self.killTrackIDs(trackIDs)
		self.markDirty()
	
	def killTrackIDs(self,trackIDs):
		FileLibrary.killTrackIDs(self,trackIDs)
		self.markDirty()

	def validate(self):
		progress = QProgressDialog("Validating","",2,None,"progress",True)
		progress.setCaption(i18n("Validating Library"))
		progress.setLabelText(i18n("Validating Library"))
		progress.setMinimumDuration(0)
		#
		# remove any tracks that don't have associated files
		#
		index = 0
		trackIDs = self._tracks.keys()
		progress.setTotalSteps(len(trackIDs))
		badIDs = []
		for trackID in trackIDs:
			track = self._tracks[trackID]
			progress.setLabelText(track.title)
			progress.setProgress(index)
			progress.forceShow()
			location = track.location
			if not os.path.exists(location):
				#progress.setLabelText(unikode(i18n("Missing %s")) % track.title)
				#progress.forceShow()
				#reprint u"missing track file for %s" % location
				badIDs.append(trackID)
				index = index+1
		self.removeTrackIDsFromAllPlaylists(badIDs)
		self.killTrackIDs(badIDs)
		
		progress.setLabelText(i18n("Scanning for orphan files"))
		progress.forceShow()
		#
		# import any files that are in the managed directories for which we don't have a track record
		#
		locations = map(lambda track: unikode(track.location), self._tracks.values())
		recurse = True
		pattern_list = None
		return_folders = False
		class Bunch:
			def __init__(self,**kwds): self.__dict__.update(kwds)
		arg = Bunch(recurse=recurse,pattern_list=pattern_list, return_folders=return_folders,results=[])
		
		def visit(arg,dirname,files):
			basename = os.path.basename(dirname)
			if basename=='.AppleDouble' or basename=='Trash':
				files[:] = []
				return
			for name in files:
				fullname = os.path.normpath(os.path.join(dirname,name))
				if os.path.isfile(fullname):
					if not unikode(fullname) in locations:
						arg.results.append(fullname)
			if not arg.recurse: files[:] = []
		os.path.walk(self.musicPath(),visit,arg)
		if len(arg.results):
			#print "orphans",arg.results
			progress.setCaption(i18n("Importing Orphans"))
			progress.setTotalSteps(len(arg.results))
			index = 0
			for location in arg.results:
				track = self.trackForFilePath(location)
				if track:
					(root,filename) = os.path.split(location)
					progress.setProgress(index)
					progress.setLabelText(filename)
					progress.forceShow()
					track.setFile(location)
					self.finishImportFile(track)
					track.managed = True
				else:
					print "Unrecognized file type",location," - skipping"
				index = index+1

	# create a playlist called "Duplicates" that contains
	def findDuplicates(self):
		progress = QProgressDialog("Checking for Duplicates","",2,None,"progress",True)
		progress.setCaption(i18n("Checking for Duplicates"))
		progress.setLabelText(i18n("Checking for Duplicates"))
		libraryTrackIDs = self.playlistWithName("Library").trackIDs
		progress.setTotalSteps(len(libraryTrackIDs))
		duplicateIDs = {}
		for index1 in xrange(0,len(libraryTrackIDs)-1):
			progress.setProgress(index1)
			trackID1 = libraryTrackIDs[index1]
			track1 = self.trackWithTrackID(trackID1)
			progress.setLabelText(track1.title)
			#print "scanning",trackID1
			for index2 in xrange(index1+1,len(libraryTrackIDs)):
				trackID2 = libraryTrackIDs[index2]
				track2 = self.trackWithTrackID(trackID2)
				if unikode(track1.title)==unikode(track2.title) and unikode(track1.artist)==unikode(track2.artist) and unikode(track1.album)==unikode(track2.album):
					#duplicateIDs[trackID1] = 1
					duplicateIDs[trackID2] = 1
		if len(duplicateIDs):
			duplicateName = unikode(i18n("Duplicates"))
			duplicatePlaylist = self.playlistWithName(duplicateName)
			if not duplicatePlaylist:
				duplicatePlaylist = self.addNewPlaylist(duplicateName)
			duplicatePlaylist.addTrackIDs(duplicateIDs.keys())
	# helper func to look for tracks with missing files
	def findUntagged(self):
		progress = QProgressDialog("Checking for non-tagged tracks","",2,None,"progress",True)
		progress.setCaption(i18n("Checking for non-tagged tracks"))
		progress.setLabelText(i18n("Checking for non-tagged tracks"))
		libraryTrackIDs = self.playlistWithName("Library").trackIDs
		progress.setTotalSteps(len(libraryTrackIDs))
		nontaggedIDs = {}
		for index1 in xrange(0,len(libraryTrackIDs)-1):
			progress.setProgress(index1)
			trackID1 = libraryTrackIDs[index1]
			track1 = self.trackWithTrackID(trackID1)
			progress.setLabelText(track1.title)
			if  unikode(track1.artist)=="" and unikode(track1.album)=="":
				nontaggedIDs[trackID1] = 1
		if len(nontaggedIDs):
			nontaggedName = unikode(i18n("Non-Tagged Tracks"))
			nontaggedPlaylist = self.playlistWithName(nontaggedName)
			if not nontaggedPlaylist:
				nontaggedPlaylist = self.addNewPlaylist(nontaggedName)
			nontaggedPlaylist.addTrackIDs(nontaggedIDs.keys())

if __name__=='__main__':
	l = MainLibrary()
	l.readXMLFile()
	print l.pList

